You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feat(evm): opt-in short-circuit for future blocks without served-tip - #33
Based on main after syncing with erpc/erpc main (WebSocket support merged). The branch has latest main merged in; the diff is only this change.
Problem
Clients that follow a chain by number (for example rollup nodes reading L1) poll eth_getBlockByNumber(head+1) before the block exists and treat null as "not yet". No upstream has the block, so each upstream returns null in turn. The request walks every routed upstream, fallbacks included, and the retry layer repeats the walk. On a network that routes all tiers in one list, almost all fallback traffic ends up being these nulls.
tryShortCircuitFutureBlock already returns null from Forward before any dispatch when the block is above every upstream's head. But it only runs on networks that enable served-tip for latest, so networks without it (the default) still fan out. Enabling served-tip to get it also changes what latest resolves to.
Change
New network option evm.shortCircuitFutureBlocks (default off, inheritable from networkDefaults.evm). The short-circuit runs when it is true or when served-tip is enabled for latest, so nothing changes for anyone who does not set it. latest is unchanged.
The short-circuit compares the requested block against futureBlockCeiling: the highest effective latest head (static caps included) across every upstream of the network. It returns null only when the block is above that, and dispatches as usual while any upstream's head is unknown. It deliberately does not try to predict routing:
selection policy is ignored, so a fallback the policy cordons off but the per-request escape can still reach counts, as do method-scoped policy slots
heads are always latest, also with emptyResultConfidence: finalizedBlock. That setting decides how an empty upstream answer is treated, not whether an upstream can have the block
So it never nulls a block any upstream reports having. The cost is that it fires less often: an upstream whose head is never learned (for example a broken endpoint) keeps it off for that network, which is today's behaviour.
The short-circuit now also runs before Forward publishes the request to the selection policy's probe bus. Before, probeExcluded still mirrored requests that were answered with the synthetic null to the excluded upstreams (28 mirrored calls for 5 short-circuited requests in the test below). The network pre-forward hook it now runs ahead of does not handle eth_getBlockByNumber, so nothing else changes order.
This also changes the short-circuit for networks that already enable served-tip for latest: it used evmHeadReference(...).Available (policy-eligible, non-syncing upstreams, finalized axis under finalizedBlock confidence). The new ceiling is never lower, so it only ever dispatches more.
Why opt-in and not just dropping the served-tip gate
The first version of this PR removed the gate. That breaks the issue 934 tests (TestNetworkAvailability_Issue934_*, TestNetworkAvailability_Window_ExactLowerUpper): the head is what eRPC last saw from the state poller and head subscriptions, so a block the node has produced since then (and a client already saw via the node's own newHeads) gets null instead of being served. That is acceptable where heads arrive promptly over WebSocket and the clients retry a null, but not as a default for every network. The docs say so.
Files
common/config.go: EvmNetworkConfig.ShortCircuitFutureBlocks and ShortCircuitFutureBlocksEnabled()
common/defaults.go: inherit from networkDefaults.evm
erpc/networks.go: gate on ShortCircuitFutureBlocksEnabled(), futureBlockCeiling, short-circuit before the probe-bus publish
docs/pages/config/projects/networks.mdx: "Future-block polling" section; agent reference schema row, networkDefaults inheritance list, edge cases 23, 24 and 31, and the future_block.short_circuit span attribute
Tests
erpc/networks_future_block_test.go (existing tests unchanged). With the option on and served-tip off:
OptInWithoutServedTip_ShortCircuitsToNull: a block above every head returns null and no upstream receives the request
OptInWithoutServedTip_OnlyMostAheadHasBlock_Dispatches: a block above the corroborated head that the most-ahead upstream has is dispatched
OptIn_SyncingUpstreamAhead_Dispatches: a syncing upstream ahead of the others means the block is dispatched
OptIn_UpstreamWithUnknownHead_Dispatches: one upstream with no known head means the block is dispatched
OptIn_CordonedFallbackAhead_Dispatches: primaries at 100, fallbacks at 110 cordoned by the default policy with failover enabled, block 105 is dispatched
OptIn_ShortCircuitNotMirroredToExcluded: default policy with cordoned fallbacks, 5 short-circuited requests reach no upstream, mirrored probes included. Fails 3/3 with the old order
The syncing, unknown-head and cordoned-fallback tests fail with the previous Available ceiling; the finalized one fails with the finalized-axis comparison. The forwarding tests register their mocks before network setup and match only the requested block number, so the state poller's polls never hit them.
common/config_test.go:
TestNetworkConfig_SetDefaults_ShortCircuitFutureBlocksInheritsFromDefaults: a true default is inherited, and an explicit network-level false overrides it
TestEvmNetworkConfig_ShortCircuitFutureBlocksEnabled: nil, unset, false, true, and served-tip for latest vs finalized only
go build ./..., go vet ./erpc (only the existing query_executor_test.go:341 warning)
go test ./common/ ./architecture/evm/ pass
go test ./erpc/ -timeout 40m on the merged branch: everything passes except two tests that also fail on plain erpc/erpc main (3/3 runs each):
- util/initializer: bound the auto-retry wait, reap tasks that overrun
their timeout, and make an attempt's start and finish atomic so a
reaped attempt can never overwrite the next one's state. Wait never
reports a failed task as succeeded, and an attempt that finds the app
context cancelled still releases the WaitGroup ExecuteTasks holds
tasksMu for.
- util/gzip_pool: closing a pooled reader also closes its source body.
- data: CounterInt64 pushes to remote only when the local value
advanced and never overwrites a higher remote value; the keep-latest
pubsub buffer keeps the fresher of two values. A refresh the caller's
deadline allows past fallbackTimeout is still applied when it lands.
- common: UniqueUpstreamKey no longer includes the network id, which is
assigned after the upstream is created and so changed the key over
the upstream's lifetime.
Co-authored-by: shpookas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- server.webSocket: buffer and message sizes, ping interval, per-
connection subscription and in-flight request limits, and the per-
subscription notification buffer; validated.
- indexer: dedupWindowSize (canonicalChainDepth is accepted and ignored).
- networks[].failover / networkDefaults.failover: onDefaultsExhausted,
with a validation-report warning when no upstream is tagged
tier:fallback.
- evm.stripSubscribeFromBlockZero, inherited from networkDefaults.
- Metrics for WebSocket connectivity, dropped subscription
notifications and the fallback escape; generated TS types.
Co-authored-by: snowkide <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
ws:// and wss:// endpoints get a persistent client instead of the
"not implemented" stub. It serves regular requests and upstream
subscriptions:
- one read loop owns the connection lifecycle; re-dials wait a jittered
exponential backoff (1s-30s) that resets only after a connection
outlives the liveness window; ping/pong plus a read deadline detect
half-open connections;
- requests fail fast with a retryable error while reconnecting and
errors are normalised exactly like the HTTP client's;
- subscription handlers are registered before the next frame is read
and are scoped to the connection that created them; eth_subscribe and
eth_unsubscribe are only sent on behalf of a subscription owner.
Upstream.IsDown checks the breaker of the policy serving a method, and
the registry exposes the WebSocket and fallback-escape upstream sets.
A response the read loop already claimed reaches its caller even if the
caller's context expired meanwhile, so a subscribe that completed is
never left without an owner; a notification for a subscription no
handler owns triggers eth_unsubscribe, which also retries a cancel that
failed. A panic in a subscription handler or callback is recovered per
frame. jsonRpc.proxyPool is refused for ws/wss upstreams, whose dialer
has no proxy support.
Co-authored-by: snowkide <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- enforceHighestBlock also applies to cache hits and keeps the caller's
use-upstream selector; when the re-fetch misses it still serves the
highest valid block.
- Cached latest/finalized entries are keyed by the block the tag
currently resolves to, so each tip advance is a new key.
- Only the observed tx_replay_attack token is treated as already known.
Co-authored-by: shpookas <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- Delivered-head floor: a head already delivered on a WebSocket
subscription, from an upstream eligible to serve the network, floors
"latest" so HTTP never regresses below it. It is bounded against live
heads, per project, skipped for use-upstream requests, never lifts
past servedTip guarantees, and shared across instances in the
background.
- failover.onDefaultsExhausted: when every routed upstream failed with
an error retryable toward the network, retry the request once on
tier:fallback upstreams not yet tried, before the selection policy
promotes them. Empty results count only for a block that should
exist; consensus is excluded. The escape honours a use-upstream pin,
and keeps the routed upstreams' empty answer if every fallback fails.
- Near-tip eth_getBlockByNumber prefers an upstream strictly ahead of
the others; requests for a specific block try upstreams that have it
first, preserving score order.
- Non-retryable writes are not multiplexed.
Co-authored-by: shpookas <[email protected]>
Co-authored-by: João Gomes <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The indexer sits between event ingresses and egresses:
- ingresses push StreamEvents; newHeads from every source update that
upstream's poller before dedup, then heads are delivered once and in
order per network;
- filter subscriptions (logs, newPendingTransactions) are refcounted
per params key so clients share one upstream subscription per
upstream, with subscribe and teardown serialised per filter;
- logs are deduplicated by blockHash/transactionHash/logIndex, with the
upstream's own removed flag tracked per log so add, remove and re-add
are each delivered once;
- adapters: wsupstream (WebSocket upstream ingress with resubscribe on
reconnect), wsclient (per-connection egress with bounded per-
subscription buffers) and nullingress (fixture ingress for tests).
Co-authored-by: snowkide <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
HTTP and WebSocket share the port and URL paths. Over WebSocket:
- single and batch requests run through the same pipeline as HTTP:
forward headers, method filters, auth, directives with
allowClientDirectives, rate limits and server.maxTimeout, with a
per-connection in-flight limit;
- eth_subscribe/eth_unsubscribe for newHeads, logs and
newPendingTransactions via the indexer, with connection-scoped
subscription ids and a per-connection subscription limit;
- a slow reader drops its own oldest newHeads/pending-tx notifications;
a logs subscription that overflows closes the connection with 1013;
- peers silent for two ping intervals are dropped; on shutdown clients
are closed with 1001 immediately while in-flight requests finish.
Only a real RFC 6455 handshake bypasses gzip and maxTimeout. The
method-not-allowed response now echoes the request id.
Every message authenticates with the upgrade request's credentials, so
an upgrade no auth strategy accepts is refused with 401; method scoping
and rate limits still apply per message. A valid notification (no id)
runs without a reply, alone or in a batch. A request that panics gets
an error carrying its own id, and errors raised before network
resolution use the URL's architecture for their wire code.
Co-authored-by: snowkide <[email protected]>
Co-authored-by: shpookas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Clients receive each head from whichever WebSocket upstream delivers it
first, eligible to the selection policy or not. Feeding the floor only
from eligible upstreams let HTTP "latest" sit a block behind a head a
subscriber had just received whenever the fastest upstream was excluded
— which a lag-based policy does routinely. Any known source whose poller
accepted the head now feeds it; the floor stays bounded against live
heads.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
On fast chains the upstream that answered "latest" one block behind is
often the one whose own feed just delivered the tip, and so the only
upstream that already has it. Excluding it made the re-fetch return
null from every other upstream and fail open to the stale block. A
corrupted tip is still caught: every upstream returns null for it and
pickHighestBlock keeps the served block.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
gosec G404 flags math/rand in the reconnect backoff. The jitter only
spreads redials; annotate it like the repo's other jitter uses.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The hedge keeper kept an ErrUpstreamsExhausted whose causes were all
missing-data, on the premise that no sibling leg could do better. A
sibling may be on an upstream this leg never tried, e.g. a leg the
fallback escape sent to a slow fallback while another leg re-swept the
primaries, so the fast miss cancelled the leg about to succeed. Such a
result now keeps the race going; if every leg ends that way the hedge
still returns the last one to the retry layer.
Co-authored-by: Vitas Spokas <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
newHeads is subscribed on every WebSocket upstream and each head goes
out from whichever source announces it first. With failover on, a
fallback-tier upstream whose WebSocket is faster told clients about a
block no primary had yet; their follow-up reads then missed on the
primaries, or escaped to the fallback while the primaries were healthy.
A fallback's head now reaches clients, and feeds the delivered-head
floor, only while no upstream outside that tier which the selection
policy routes to has a live newHeads subscription of its own. "Down"
is the policy's verdict, as for reads, so custom policies that keep
fallbacks in the ordered list behave the same. A network whose only
WebSocket upstreams are fallbacks, or whose primaries' subscriptions
are dead, keeps receiving fallback heads. A held-back head still
updates that fallback's state poller.
NetworkHandle.SuggestLatestBlock now reports whether the head may be
delivered; the indexer drops it before dedup otherwise.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
logs and newPendingTransactions picked their upstreams once, when the
first client subscribed: on every primary whose eth_subscribe breaker
was closed, on the fallbacks only if every primary rejected the
subscribe. Nothing revisited that for the filter's lifetime, so a
filter went silent when its primaries dropped it, stayed on the
fallbacks after the primaries recovered, and never reached a primary
whose first subscribe failed.
A filter now belongs on every primary the selection policy routes to,
and on the fallback tier while none of those has it live; the indexer
rechecks this on every head, adding and removing the filter as that
changes. The fallbacks hand a filter back only once a primary has it
live, so there is no gap. An upstream whose subscribe fails keeps the
filter and retries it in the background, like its other subscriptions.
Only heads a tip tracker has accepted reach subscribers: a major jump is
verified before it is delivered, and a source with no tracker is
withheld, so an unverified head can't advance the indexer's dedup
marker. The upstream eth_unsubscribe outlives a caller that already
gave up but keeps a live caller deadline.
The indexer and the subscription manager key a network by project and
network, so two projects that define the same network keep their own
upstreams. A filter subscribe is bounded by the attempt timeout, since
callers hold locks across it and a dropped reply on a busy connection
would otherwise never time out.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
tryShortCircuitFutureBlock returns null for a numbered eth_getBlockByNumber
above the highest head any eligible upstream has (static caps included), so
it never nulls a block an upstream has. It only runs when served-tip is
enabled for latest.
Clients that poll the next block by number get a null from each upstream in
turn on networks without served-tip, so every poll walks every routed
upstream, fallbacks included, and the retry layer repeats the walk.
Add evm.shortCircuitFutureBlocks to enable the short-circuit on its own,
without changing what latest resolves to. It is off by default: the head is
what eRPC last saw, so a block produced since then can briefly get null
(the issue 934 case), which only suits networks whose heads arrive promptly.
shpookas
changed the title
fix(network): short-circuit future blocks without served-tip
feat(evm): opt-in short-circuit for future blocks without served-tip
Oct 9, 2026
The future-block short-circuit compares against the head reference, which
leaves syncing upstreams out. An upstream that reports syncing still receives
requests unless evm.skipWhenSyncing is set, so it may already have a block
above that reference. Dispatch when one of them is at or above the block.
Also:
- test networkDefaults inheritance and an explicit false override
- register gock mocks before network setup in the new forwarding tests
- docs: config schema row, edge cases, span attribute, comment style
The future-block short-circuit used finalized heads when
emptyResultConfidence is finalizedBlock, so a block between an upstream's
finalized and latest head returned null without being dispatched.
emptyResultConfidence decides how an empty upstream answer is treated; it
should not decide whether an upstream can have the block. Always compare
against latest heads, including for dispatchable syncing upstreams.
Fail open when any dispatchable upstream has an unknown head
erpc/networks.go:940
Available only fails open when every candidate lacks a head; evmTipBallot silently drops each individual upstream whose poller/effective latest head is unknown. With one known upstream at 100 and another dispatchable upstream with an unknown head, block 101 is therefore synthesized as null even though the latter may already serve it. Before short-circuiting, fail open if any potentially dispatchable candidate has no positive latest-head observation, and add a mixed known/unknown regression case.
Include fallback-escape upstreams when computing the request ceiling
erpc/networks.go:943
This ceiling uses the wildcard selection-policy candidates rather than every upstream this request may dispatch to. The default policy excludes tier:fallback, but onDefaultsExhausted can later add those upstreams via GetFallbackEscapeUpstreams; thus primaries at head 100 plus a fallback at 110 will return an undispatched null for block 105 instead of letting the fallback serve it. Method-/boundary-scoped policy slots can similarly differ from the wildcard slot. Build the ceiling from the selected request upstreams plus eligible fallback-escape candidates.
The future-block ceiling came from the selection policy's wildcard
candidates plus a separate pass over syncing upstreams. That missed
upstreams a request can still reach: fallbacks the policy cordons off but
the per-request escape brings back, and method-scoped policy slots. It also
skipped upstreams with an unknown head, so a block one of them already had
returned null.
Compute the ceiling over every upstream of the network instead, regardless
of policy, tier or syncing state, and fail open while any upstream's head is
unknown. Replaces dispatchableSyncingHead.
Addressed both "previously missed" findings from the last review in 8d4b27d. Instead of mirroring routing (policy candidates per method, fallback escape, skipWhenSyncing), the ceiling is now the highest latest head across every upstream of the network, and the short-circuit fails open while any upstream's head is unknown:
fallback cordoned by policy but reachable through the escape: OptIn_CordonedFallbackAhead_Dispatches
Both fail with the previous Available ceiling. Trade-off: an upstream whose head is never learned keeps the short-circuit off for that network (documented in edge case 24).
Forward publishes this request to the policy probe bus before calling this helper (erpc/networks.go:2173-2175). Because the default policy enables probeExcluded, an excluded upstream can still receive an asynchronous mirrored eth_getBlockByNumber even when this returns the synthetic null, contradicting the option's no-dispatch guarantee and preserving upstream load. Move probe publication after the future-block short-circuit, and cover an active excluded-upstream prober in the test.
Forward published each request to the selection policy's probe bus before
the future-block short-circuit, so probeExcluded still mirrored requests
that were answered with a synthetic null to excluded upstreams. Run the
short-circuit first. It only matches eth_getBlockByNumber, which the
network pre-forward hook does not handle, so nothing else moves.
Also describe the ceiling as every upstream of the network in the config
comment, TypeScript type and docs, and point edge case 24 at the
short-circuit code.
probe dispatch: the short-circuit now runs before the probe-bus publish, so a synthetic null is never mirrored to excluded upstreams. OptIn_ShortCircuitNotMirroredToExcluded fails 3/3 with the old order (28 mirrored calls for 5 requests) and passes with the fix. The network pre-forward hook it now runs ahead of does not handle eth_getBlockByNumber.
"eligible" wording: the config comment, TypeScript type and docs snippets now say every upstream of the network, including that it never fires while any upstream's head is unknown.
edge case 24 now links to tryShortCircuitFutureBlock and futureBlockCeiling.
Limit dispatch guarantee to latest observed effective heads
docs/pages/config/projects/networks.mdx:104
“A block any upstream has is still dispatched” contradicts the documented stale-head behavior below: an upstream may have produced the block after its last reported head, in which case this option returns null. Limit this guarantee to the latest observed effective heads.
Clarify safety claim to reference last observed effective heads
erpc/networks.go:890
This safety claim is stronger than the implementation: the ceiling uses the last observed heads, so the function can return null for a block that an upstream already serves but whose poller/subscription has not reported it yet (the PR documents this stale-head case). Phrase this in terms of the last effective-head observations rather than actual upstream capability so the function contract does not contradict the opt-in caveat.
The short-circuit compares against the last heads eRPC observed, so it never nulls a block at or below a reported head, but a block produced after the last observed head can still get null. Say that in the tryShortCircuitFutureBlock comment and the docs instead of promising any block an upstream has is dispatched.
Addressed the two wording findings in dbecbcb: the tryShortCircuitFutureBlock comment and the docs now state the guarantee in terms of the last observed heads (never nulls a block at or below a head an upstream has reported), and say that a block produced after the last observed head can still get null, matching the stale-head caveat. Comment and docs only, no logic change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Based on
mainafter syncing with erpc/erpc main (WebSocket support merged). The branch has latestmainmerged in; the diff is only this change.Problem
Clients that follow a chain by number (for example rollup nodes reading L1) poll
eth_getBlockByNumber(head+1)before the block exists and treatnullas "not yet". No upstream has the block, so each upstream returnsnullin turn. The request walks every routed upstream, fallbacks included, and the retry layer repeats the walk. On a network that routes all tiers in one list, almost all fallback traffic ends up being these nulls.tryShortCircuitFutureBlockalready returnsnullfromForwardbefore any dispatch when the block is above every upstream's head. But it only runs on networks that enable served-tip forlatest, so networks without it (the default) still fan out. Enabling served-tip to get it also changes whatlatestresolves to.Change
New network option
evm.shortCircuitFutureBlocks(default off, inheritable fromnetworkDefaults.evm). The short-circuit runs when it is true or when served-tip is enabled forlatest, so nothing changes for anyone who does not set it.latestis unchanged.The short-circuit compares the requested block against
futureBlockCeiling: the highest effective latest head (static caps included) across every upstream of the network. It returnsnullonly when the block is above that, and dispatches as usual while any upstream's head is unknown. It deliberately does not try to predict routing:skipWhenSyncingsaysemptyResultConfidence: finalizedBlock. That setting decides how an empty upstream answer is treated, not whether an upstream can have the blockSo it never nulls a block any upstream reports having. The cost is that it fires less often: an upstream whose head is never learned (for example a broken endpoint) keeps it off for that network, which is today's behaviour.
The short-circuit now also runs before
Forwardpublishes the request to the selection policy's probe bus. Before,probeExcludedstill mirrored requests that were answered with the syntheticnullto the excluded upstreams (28 mirrored calls for 5 short-circuited requests in the test below). The network pre-forward hook it now runs ahead of does not handleeth_getBlockByNumber, so nothing else changes order.This also changes the short-circuit for networks that already enable served-tip for
latest: it usedevmHeadReference(...).Available(policy-eligible, non-syncing upstreams, finalized axis underfinalizedBlockconfidence). The new ceiling is never lower, so it only ever dispatches more.Why opt-in and not just dropping the served-tip gate
The first version of this PR removed the gate. That breaks the issue 934 tests (
TestNetworkAvailability_Issue934_*,TestNetworkAvailability_Window_ExactLowerUpper): the head is what eRPC last saw from the state poller and head subscriptions, so a block the node has produced since then (and a client already saw via the node's ownnewHeads) getsnullinstead of being served. That is acceptable where heads arrive promptly over WebSocket and the clients retry anull, but not as a default for every network. The docs say so.Files
common/config.go:EvmNetworkConfig.ShortCircuitFutureBlocksandShortCircuitFutureBlocksEnabled()common/defaults.go: inherit fromnetworkDefaults.evmerpc/networks.go: gate onShortCircuitFutureBlocksEnabled(),futureBlockCeiling, short-circuit before the probe-bus publishtypescript/config/src/generated.ts:shortCircuitFutureBlocks?: booleandocs/pages/config/projects/networks.mdx: "Future-block polling" section; agent reference schema row,networkDefaultsinheritance list, edge cases 23, 24 and 31, and thefuture_block.short_circuitspan attributeTests
erpc/networks_future_block_test.go(existing tests unchanged). With the option on and served-tip off:OptInWithoutServedTip_ShortCircuitsToNull: a block above every head returnsnulland no upstream receives the requestOptInWithoutServedTip_OnlyMostAheadHasBlock_Dispatches: a block above the corroborated head that the most-ahead upstream has is dispatchedOptIn_SyncingUpstreamAhead_Dispatches: a syncing upstream ahead of the others means the block is dispatchedOptIn_UpstreamWithUnknownHead_Dispatches: one upstream with no known head means the block is dispatchedOptIn_CordonedFallbackAhead_Dispatches: primaries at 100, fallbacks at 110 cordoned by the default policy with failover enabled, block 105 is dispatchedOptIn_FinalizedConfidence_UnfinalizedBlock_Dispatches:emptyResultConfidence: finalizedBlock, finalized 90, latest 98-100, block 95 is dispatchedOptIn_ShortCircuitNotMirroredToExcluded: default policy with cordoned fallbacks, 5 short-circuited requests reach no upstream, mirrored probes included. Fails 3/3 with the old orderThe syncing, unknown-head and cordoned-fallback tests fail with the previous
Availableceiling; the finalized one fails with the finalized-axis comparison. The forwarding tests register their mocks before network setup and match only the requested block number, so the state poller's polls never hit them.common/config_test.go:TestNetworkConfig_SetDefaults_ShortCircuitFutureBlocksInheritsFromDefaults: atruedefault is inherited, and an explicit network-levelfalseoverrides itTestEvmNetworkConfig_ShortCircuitFutureBlocksEnabled: nil, unset, false, true, and served-tip forlatestvsfinalizedonlygo build ./...,go vet ./erpc(only the existingquery_executor_test.go:341warning)go test ./common/ ./architecture/evm/passgo test ./erpc/ -timeout 40mon the merged branch: everything passes except two tests that also fail on plain erpc/erpc main (3/3 runs each):TestHttpServer_H2C_ShutdownGoAwaysAndDrainsInFlightStreamTestNetwork_Forward/ForwardLlamaRPCEndpointRateLimitResponseSingle